﻿namespace SharpF2.ModelView
{
	using System;
	using System.Collections.Generic;
	using System.Collections.ObjectModel;
	using System.ComponentModel;
	using System.Linq;
	using System.Text;
	using System.Text.RegularExpressions;
	using System.Threading;
	using System.Windows.Threading;
	using Components;
	using Services;

	public partial class MainViewViewModel
	{
		#region Private Attributes

		private readonly ItemMetaData<FileItem> fileColumnsErrorMessage = new ItemMetaData<FileItem>
		                                                                  	{
		                                                                  		HeaderBindingName = "ErrorMessage",
		                                                                  		PropertyName = "ErrorMessage",
		                                                                  		Sortable = false
		                                                                  	};

		private readonly ItemMetaData<FileItem> fileColumnsFileIndex = new ItemMetaData<FileItem>
		                                                               	{
		                                                               		HeaderBindingName = "FileIndex",
		                                                               		PropertyName = "FileIndex",
		                                                               		Comparer =
		                                                               			(x, y) => x.FileIndex.CompareTo(y.FileIndex),
		                                                               		Sortable = false
		                                                               	};

		private readonly ItemMetaData<FileItem> fileColumnsInputFileName = new ItemMetaData<FileItem>
		                                                                   	{
		                                                                   		HeaderBindingName = "InputFileName",
		                                                                   		PropertyName = "InputFileName",
		                                                                   		Comparer =
		                                                                   			(x, y) =>
		                                                                   			x.InputFileName.CompareTo(y.InputFileName),
		                                                                   		Sortable = true
		                                                                   	};

		private readonly ItemMetaData<FileItem> fileColumnsOutputFileName = new ItemMetaData<FileItem>
		                                                                    	{
		                                                                    		HeaderBindingName = "OutputFileName",
		                                                                    		PropertyName = "OutputFileName",
		                                                                    		InConflictBinding = "InConflict",
		                                                                    		Sortable = false
		                                                                    	};

		private readonly ItemMetaData<TextItem> textColumnsErrorMessage = new ItemMetaData<TextItem>
		                                                                  	{
		                                                                  		HeaderBindingName = "ErrorMessage",
		                                                                  		PropertyName = "ErrorMessage",
		                                                                  		Sortable = false
		                                                                  	};

		private readonly ItemMetaData<TextItem> textColumnsText = new ItemMetaData<TextItem>
		                                                          	{
		                                                          		HeaderName = "Text",
		                                                          		PropertyName = "Text",
		                                                          		Comparer = (x, y) => x.Text.CompareTo(y.Text),
		                                                          		Sortable = false
		                                                          	};

		#endregion

		#region Private Members

		/// <summary>
		/// Check for conflicts in the FileItems and update NbConflicts.
		/// </summary>
		private static Int32 CheckForConflicts(IEnumerable<FileItem> fileItems)
		{
			if (null == fileItems)
				return 0;

			DirectoryScannerManager.Instance.UpdateConflicts(ref fileItems);

			return fileItems.Count(i => i.InConflict);
		}

		/// <summary>
		/// List all the Identifiers contained in the RegexFormat.
		/// </summary>
		/// <param name="input">Text input containing identifiers</param>
		/// <param name="identifiers">The identifiers collection to fill</param>
		private static void ExtractIdentifiers(string input, ref IEnumerable<IdentifierItem> identifiers)
		{
			var oldIdentifiers = new Dictionary<String, IdentifierItem>();
			foreach (var identifier in identifiers)
				if (!oldIdentifiers.ContainsKey(identifier.IdentifierId))
					oldIdentifiers[identifier.IdentifierId] = identifier;

			var newIdentifiers = new List<String>();
			var localIdentifiers = new List<IdentifierItem>();
			if (String.IsNullOrEmpty(input)) return;

			var tokens = Regex.Split(input, "(<[a-zA-Z0-9]+)>");
			foreach (var token in tokens)
				if (token.Length > 1 && token[0] == '<')
				{
					var identifierId = token.Substring(1);
					IdentifierItem identifier;
					if (oldIdentifiers.TryGetValue(identifierId, out identifier))
					{
						localIdentifiers.Add(identifier);
						oldIdentifiers.Remove(identifierId);
					}
					else
						newIdentifiers.Add(identifierId);
				}

			if (oldIdentifiers.Count == 1 && newIdentifiers.Count == 1)
			{
				var uniqueIdentifers = oldIdentifiers.First();
				uniqueIdentifers.Value.IdentifierId = newIdentifiers.First();
				localIdentifiers.Add(uniqueIdentifers.Value);
			}
			else
				foreach (var newIdentifier in newIdentifiers)
					localIdentifiers.Add(IdentifierItem.GetIdentifierItem(newIdentifier));

			localIdentifiers.Sort();

			identifiers = localIdentifiers;
		}

		private static Boolean MergeIdentifiers(
			IEnumerable<IdentifierItem> fileId,
			IEnumerable<IdentifierItem> textId,
			out IEnumerable<IdentifierItem> identifiers,
			out String error)
		{
			error = String.Empty;

			var currentIdentifers = new List<IdentifierItem>(fileId);
			currentIdentifers.AddRange(textId);

			var nextIdentifers = new List<IdentifierItem>();

			var conflicts = false;
			var identifiersDictionary = new Dictionary<String, IdentifierItem>();
			var errorMessage = new StringBuilder("Error remove duplicates : ");
			foreach (var identifier in currentIdentifers)
			{
				if (identifiersDictionary.ContainsKey(identifier.IdentifierId))
				{
					conflicts = true;
					errorMessage.Append(String.Format("{0}, ", identifier.IdentifierId));
				}
				else
				{
					identifiersDictionary[identifier.IdentifierId] = identifier;
					nextIdentifers.Add(identifier);
				}
			}

			if (conflicts)
			{
				error = errorMessage.ToString().Substring(0, errorMessage.Length - 2);
				identifiers = new ObservableCollection<IdentifierItem>();
				return false;
			}

			identifiers = nextIdentifers;
			return true;
		}

		private static Boolean TryGetRegex(String regexPattern, out Regex regex, out String errorMessage)
		{
			try
			{
				regex = new Regex(regexPattern, RegexOptions.Compiled);
				errorMessage = String.Empty;
				return true;
			}
			catch (Exception e)
			{
				regex = null;
				errorMessage = e.Message;
				return false;
			}
		}

		private static void UpdateFileIndexs(IEnumerable<FileItem> files, Int32 startIndex)
		{
			var filesArray = files.ToArray();
			var j = 0;
			for (var i = startIndex; j < files.Count(); ++i, ++j)
				filesArray[j].FileIndex = i;
		}

		private static void UpdateItemIdentifiers<T>(IEnumerable<IdentifierItem> itemIdentifiers, IEnumerable<T> items,
		                                             Regex regex, out int fails) where T : Item
		{
			fails = 0;
			foreach (var item in items)
				if (item.UpdateIdentifiers(regex, itemIdentifiers))
					++fails;
		}

		private static void UpdateOrder(IEnumerable<FileItem> files, IEnumerable<TextItem> texts)
		{
			var textItem = texts.GetEnumerator();
			var fileItem = files.GetEnumerator();
			while (textItem.MoveNext() && fileItem.MoveNext())
				fileItem.Current.InputTextItem = textItem.Current;
			while (fileItem.MoveNext())
				fileItem.Current.InputTextItem = null;
		}

		private static void UpdateOutputFilenames(IEnumerable<FileItem> fileItems, string outputFilePatternText,
		                                          out int conflics)
		{
			var outPatternstrings = Regex.Split(outputFilePatternText, "(\\${[a-zA-Z0-9]+})|([\'#\']+)", RegexOptions.Compiled);

			foreach (var fileItem in fileItems)
				fileItem.UpdateOutputFileName(outPatternstrings);

			conflics = CheckForConflicts(fileItems);
		}

		private void IdentifierChanged()
		{
			var localFileItems = FileItems.ToList();
			var localTextItems = TextItems.ToList();
			var localFileIdentifierItems = fileIdentifiers.ToList();
			var localTextIdentifierItems = textIdentifiers.ToList();
			var localOutputPattern = OutputFilePatternText;
			var localStartIndex = startIndex;
			var fileFails = 0;
			var textFails = 0;
			var conflics = 0;

			var bw = new BackgroundWorker();
			bw.DoWork += (o, e) =>
			             	{
			             		if (null != regexInputFile)
			             			UpdateItemIdentifiers(localFileIdentifierItems, localFileItems, regexInputFile, out fileFails);
			             		if (null != regexInputText)
			             			UpdateItemIdentifiers(localTextIdentifierItems, localTextItems, regexInputText, out textFails);
			             		UpdateOrder(localFileItems, localTextItems);
			             		UpdateFileIndexs(localFileItems, localStartIndex);
			             		UpdateOutputFilenames(localFileItems, localOutputPattern, out conflics);
			             	};

			bw.RunWorkerCompleted += (o, e) =>
			                         	{
			                         		NbFileRegexFails = fileFails;
			                         		NbTextRegexFails = textFails;
			                         		NbConflicts = conflics;
			                         		IsRenamable = 0 == NbFileRegexFails && 0 == NbTextRegexFails && 0 == NbConflicts &&
			                         		              0 < fileItems.Count;

			                         		FileItems.OnInnerDataChanged();
			                         		TextItems.OnInnerDataChanged();

			                         		WorkingInBackground = false;
			                         	};

			bw.RunWorkerAsync();
		}

		private void InitializeIdentifierColumns()
		{
			IdentifierColumns.Clear();
			IdentifierColumns.Add(new ItemMetaData<IdentifierItem>
			                      	{
			                      		HeaderBindingName = "Identifiers",
			                      		PropertyName = "IdentifierId",
			                      		Sortable = false
			                      	});

			OnPropertyChanged("IdentifierColumns");
		}


		private void OnUpdateFileCompleted(UpdateIdentifiersResult result)
		{
			RegexInputFileFormatErrorMessage = result.Error;

			fileIdentifiers = new List<IdentifierItem>(result.ItemIdentifiers ?? new List<IdentifierItem>());
			Identifiers = new ObservableCollection<IdentifierItem>(result.Identifiers ?? new List<IdentifierItem>());

			NbFileRegexFails = result.Fails;
			NbConflicts = result.Conflics;

			IsRenamable = 0 == NbFileRegexFails && 0 == NbTextRegexFails && 0 == NbConflicts && 0 < fileItems.Count;

			regexInputFile = result.Regex;

			FileItems.OnInnerDataChanged();

			WorkingInBackground = false;

			dispatcher.BeginInvoke(DispatcherPriority.Normal, new ThreadStart(UpdateFileColumns));
		}

		private void OnUpdateTextCompleted(UpdateIdentifiersResult result)
		{
			RegexInputTextFormatErrorMessage = result.Error;

			textIdentifiers = new List<IdentifierItem>(result.ItemIdentifiers ?? new List<IdentifierItem>());
			Identifiers = new ObservableCollection<IdentifierItem>(result.Identifiers ?? new List<IdentifierItem>());

			NbTextRegexFails = result.Fails;
			NbConflicts = result.Conflics;

			IsRenamable = 0 == NbFileRegexFails && 0 == NbTextRegexFails && 0 == NbConflicts && 0 < fileItems.Count;

			regexInputText = result.Regex;

			TextItems.OnInnerDataChanged();

			WorkingInBackground = false;

			dispatcher.BeginInvoke(DispatcherPriority.Normal, new ThreadStart(UpdateTextColumns));
		}

		private void OutputFilePatternChanged()
		{
			var localFileItems = FileItems.ToList();
			var localTextItems = TextItems.ToList();
			var localOutputPattern = OutputFilePatternText;
			var localStartIndex = startIndex;
			var fails = 0;
			var conflics = 0;

			var bw = new BackgroundWorker();
			bw.DoWork += (o, e) =>
			             	{
			             		UpdateOrder(localFileItems, localTextItems);
			             		UpdateFileIndexs(localFileItems, localStartIndex);
			             		UpdateOutputFilenames(localFileItems, localOutputPattern, out conflics);
			             	};

			bw.RunWorkerCompleted += (o, e) =>
			                         	{
			                         		NbFileRegexFails = fails;
			                         		NbConflicts = conflics;
			                         		IsRenamable = 0 == NbFileRegexFails && 0 == NbTextRegexFails && 0 == NbConflicts &&
			                         		              0 < fileItems.Count;

			                         		FileItems.OnInnerDataChanged();
			                         		TextItems.OnInnerDataChanged();

			                         		WorkingInBackground = false;
			                         	};

			bw.RunWorkerAsync();
		}

		private void UpdateColumns()
		{
			UpdateTextColumns();
			UpdateFileColumns();
		}

		private void UpdateFileColumns()
		{
			var columns = new ObservableCollection<ItemMetaData<FileItem>> {fileColumnsFileIndex, fileColumnsInputFileName};

			foreach (var identifier in fileIdentifiers)
			{
				var identifierLocal = identifier.IdentifierId;
				columns.Add(new ItemMetaData<FileItem>
				            	{
				            		HeaderName = identifierLocal,
				            		PropertyName = String.Format("IdentifiersValue[{0}]", identifierLocal),
				            		Comparer =
				            			(x, y) => x.IdentifiersValue[identifierLocal].CompareTo(y.IdentifiersValue[identifierLocal]),
				            		Sortable = true
				            	});
			}

			columns.Add(fileColumnsOutputFileName);

			columns.Add(fileColumnsErrorMessage);

			FileColumns = columns;
		}

		private void UpdateFileIdentifiers(String regexPattern)
		{
			UpdateItems(regexPattern, fileItems, fileIdentifiers, textIdentifiers, OnUpdateFileCompleted);
		}

		private void UpdateItems<T>(
			String regexPattern,
			IEnumerable<T> items,
			IEnumerable<IdentifierItem> firstIdentifierItem,
			IEnumerable<IdentifierItem> secondIdentifierItem,
			Action<UpdateIdentifiersResult> onCompleted) where T : Item
		{
			var localFileItems = FileItems.ToList();
			var localTextItems = TextItems.ToList();
			var localOutputPattern = OutputFilePatternText;
			var localStartIndex = startIndex;

			var bw = new BackgroundWorker();
			bw.DoWork += (o, e) =>
			             	{
			             		var localResult = new UpdateIdentifiersResult
			             		                  	{Fails = items.Count(), Conflics = 0, ItemIdentifiers = firstIdentifierItem};

			             		Regex regex;
			             		string localErrorMessage;
			             		IEnumerable<IdentifierItem> localIdentifiers;
			             		int fails;
			             		int conflics;

			             		ExtractIdentifiers(regexPattern, ref firstIdentifierItem);
			             		localResult.ItemIdentifiers = firstIdentifierItem;
			             		if (
			             			!MergeIdentifiers(firstIdentifierItem, secondIdentifierItem, out localIdentifiers,
			             			                  out localErrorMessage))
			             		{
			             			localResult.Error = localErrorMessage;
			             			e.Result = localResult;
			             			return;
			             		}
			             		localResult.Identifiers = localIdentifiers;

			             		var result = TryGetRegex(regexPattern, out regex, out localErrorMessage);
			             		if (!result)
			             		{
			             			localResult.Error = localErrorMessage;
			             			e.Result = localResult;
			             			return;
			             		}
			             		localResult.Regex = regex;

			             		UpdateItemIdentifiers(firstIdentifierItem, items, regex, out fails);

			             		if (null == localOutputPattern)
			             		{
			             			localResult.Error = localErrorMessage;
			             			e.Result = localResult;
			             			return;
			             		}

			             		UpdateOrder(localFileItems, localTextItems);
			             		UpdateFileIndexs(localFileItems, localStartIndex);
			             		UpdateOutputFilenames(localFileItems, localOutputPattern, out conflics);

			             		localResult.Error = localErrorMessage;
			             		localResult.Fails = fails;
			             		localResult.Conflics = conflics;
			             		e.Result = localResult;
			             	};

			bw.RunWorkerCompleted += (o, e) =>
			                         	{
			                         		var result = e.Result as UpdateIdentifiersResult;
			                         		if (null == result) return;

			                         		onCompleted(result);
			                         	};

			bw.RunWorkerAsync();
		}

		/// <summary>
		/// Update optional column from an identifiers collection.
		/// </summary>
		private void UpdateTextColumns()
		{
			var columns = new ObservableCollection<ItemMetaData<TextItem>> {textColumnsText};

			foreach (var identifier in textIdentifiers)
			{
				var identifierLocal = identifier.IdentifierId;
				columns.Add(new ItemMetaData<TextItem>
				            	{
				            		HeaderName = identifierLocal,
				            		PropertyName = String.Format("IdentifiersValue[{0}]", identifierLocal),
				            		Comparer =
				            			(x, y) => x.IdentifiersValue[identifierLocal].CompareTo(y.IdentifiersValue[identifierLocal]),
				            		Sortable = true
				            	});
			}

			columns.Add(textColumnsErrorMessage);

			TextColumns = columns;
		}

		private void UpdateTextIdentifiers(String regexPattern)
		{
			UpdateItems(regexPattern, textItems, textIdentifiers, fileIdentifiers, OnUpdateTextCompleted);
		}

		#endregion

		#region Nested type: UpdateIdentifiersResult

		private class UpdateIdentifiersResult
		{
			#region Public Properties

			public Int32 Conflics { get; set; }
			public String Error { get; set; }
			public Int32 Fails { get; set; }
			public IEnumerable<IdentifierItem> Identifiers { get; set; }
			public IEnumerable<IdentifierItem> ItemIdentifiers { get; set; }
			public Regex Regex { get; set; }

			#endregion
		}

		#endregion
	}
}